package com.dsunsoft.rtu.netty;

import com.dsunsoft.rtu.properties.AppProperties;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.logging.ByteBufFormat;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class NettyServer implements CommandLineRunner {

    @Autowired
    private AppProperties properties;

    /**
     * 启动 netty
     */
    //@Async
    @Override
    public void run(String... args) throws Exception {
        try {
            NettyServer server = new NettyServer();
            server.start(properties.getPorts());
        } catch (Exception e) {
            log.error("netty 启动出错: ", e);
            throw e;
        }
    }

    /**
     * 创建两个线程组 bossGroup 和 workerGroup
     * bossGroup 只是处理连接请求，真正的和客户端业务处理，会交给 workerGroup 完成
     * 两个都是无线循环
     */
    public void start(int... ports) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // 创建服务器端的启动对象，配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 设置两个线程组
            bootstrap.group(bossGroup, workerGroup)
                    // 使用NioServerSocketChannel 作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)
                    // 设置线程队列得到连接个数
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    // 设置保持活动连接状态
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // 通过NoDelay禁用Nagle,使消息立即发出去，不用等待到一定的数据量才发出去
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 可以给 bossGroup 加个日志处理器
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 动态缓冲池大小,消息大小不固定
                    .childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(32, 64, 65536))
                    // 固定缓冲池大小,1024kb
                    //.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(1024 * 1024))
                    // 给workerGroup 的 EventLoop 对应的管道设置处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //给pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline p = socketChannel.pipeline();
                            // 自定义编码器，用于补全消息，解决粘包问题
                            // *注意自定义长度处理器要写在首位*
                            // 1.单个包最大长度
                            // 2.长度字段开始位置偏移量
                            // 3.长度字段所占字节数
                            // 4.长度字段默认表示后续报文的长度
                            // 5.解码过程中不丢弃任何数据
                            // 6.默认为true，表示读取到长度域的值超过Integer.MAX_VALUE时抛异常，false表示真正读取完才抛异常，建议不要修改，否则可能会造成内存溢出
                            p.addLast(new LoggingHandler(LogLevel.DEBUG, ByteBufFormat.HEX_DUMP));
                            p.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 4, 2,0,0,true));
                            p.addLast("decoder", new ByteArrayDecoder());
                            p.addLast("encoder", new ByteArrayEncoder());
                            p.addLast(new NettyServerHandler());
                        }
                    });

            // 多端口启动服务器并绑定一个端口并且同步生成一个 ChannelFuture 对象
            ChannelFuture[] channelFutures = new ChannelFuture[ports.length];
            for (int i = 0; i < ports.length; i++) {
                ChannelFuture future = bootstrap.bind(ports[i]).sync();
                if (future.isSuccess()) {
                    log.info("netty server port {} starting ...", ports[i]);
                } else {
                    log.info("netty server port {} fail ...", ports[i]);
                }
                channelFutures[i] = future;
            }

            // 对关闭通道进行监听
            for (ChannelFuture future : channelFutures) {
                future.channel().closeFuture().sync();
            }

        } finally {
            // 发送异常关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
